{ "cells": [ { "cell_type": "markdown", "id": "bbba7248", "metadata": {}, "source": [ "# QCoDeS example with Galil DMC4133 Controller" ] }, { "cell_type": "markdown", "id": "86f9a772", "metadata": {}, "source": [ "Purpose of this notebook is to demonstrate how Galil DMC4133 Controller driver along with Arm class can be used for running measurements while controlling the arm head simulteneously. Before begining,\n", "\n", "1. Make sure that you have `gclib` package installed in your environment. If not, then follow the instructions [here](https://www.galil.com/sw/pub/all/doc/gclib/html/python.html) for installation.\n", "2. Make sure that the controller is connected to your PC through an Ethernet cable and the configuration is set as according to these instructions on Windows operating system.\n", " \n", " a. Go to Control Panel -> Network and Internet -> Network Connections and select the appropriate network adapter.\n", " b. Next go to the Properties of that adapter, and then Properties for Internet Protocol Version 4 (TCP/IPv4).\n", " c. Select \"Use the following IP address\" and add an IP address and Subnet. (If the Galil has an IP address of 10.10.10.100 burned in, you would need a PC IP address of something like 10.10.10.1 with a subnet of 255.255.255.0.)\n", "\n", "Once, the connection to the DMC4133 Controller is established, we can begin with necessary imports and the calibration process. So, let us begin!" ] }, { "attachments": {}, "cell_type": "markdown", "id": "7ac43cd2", "metadata": {}, "source": [ "## Imports" ] }, { "cell_type": "code", "execution_count": null, "id": "fcc8353a", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from qcodes.instrument_drivers.Galil import GalilDMC4133Arm, GalilDMC4133Controller" ] }, { "attachments": {}, "cell_type": "markdown", "id": "34a0df1a", "metadata": {}, "source": [ "## DMC4133 Controller" ] }, { "cell_type": "code", "execution_count": 2, "id": "b282c1b4", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:18:56.092696Z", "start_time": "2021-09-06T12:18:56.052425Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Connected to: Galil Motion Control, Inc. DMC4133 (serial:16639, firmware:1.3f) in 0.03s\n" ] } ], "source": [ "controller = GalilDMC4133Controller(name='controller', address='192.168.8.74')" ] }, { "cell_type": "markdown", "id": "6df77e29", "metadata": {}, "source": [ "The controller with 3 motors can be initialized as above. Now lets discuss the features available on the QCoDeS DMC4133 Controller driver.\n", "\n", "We can find out the absolute position of the controller with following call." ] }, { "cell_type": "code", "execution_count": null, "id": "b777aeaa", "metadata": {}, "outputs": [], "source": [ "controller.absolute_position()" ] }, { "cell_type": "markdown", "id": "ef02ea25", "metadata": {}, "source": [ "Current position of the controller can ber defined as origin with command:" ] }, { "cell_type": "code", "execution_count": null, "id": "576e2940", "metadata": {}, "outputs": [], "source": [ "controller.define_position_as_origin()" ] }, { "cell_type": "markdown", "id": "572298ea", "metadata": {}, "source": [ "`tell_error` method on the controller can be used to get which error occured in case it does in a human readable form." ] }, { "cell_type": "code", "execution_count": null, "id": "cdb774b5", "metadata": {}, "outputs": [], "source": [ "controller.tell_error()" ] }, { "cell_type": "markdown", "id": "c6e3ba71", "metadata": {}, "source": [ "`stop` method can be used to stop motion of all motors simultaneously. On this call, the motors will decelerate to a stop. " ] }, { "cell_type": "code", "execution_count": null, "id": "901b6cb0", "metadata": {}, "outputs": [], "source": [ "controller.stop()" ] }, { "cell_type": "markdown", "id": "e376f930", "metadata": {}, "source": [ "`abort` call can be used to abort any motion on all three motors simultaneously. On this call, the motors will immediately come to a halt." ] }, { "cell_type": "code", "execution_count": null, "id": "186b696f", "metadata": {}, "outputs": [], "source": [ "controller.abort()" ] }, { "cell_type": "markdown", "id": "37194757", "metadata": {}, "source": [ "`motors_off` call turns all motors off simultaneously." ] }, { "cell_type": "code", "execution_count": null, "id": "08a62b37", "metadata": {}, "outputs": [], "source": [ "controller.motors_off()" ] }, { "cell_type": "markdown", "id": "ac95fc3b", "metadata": {}, "source": [ "`wait_till_motion_complete` method call is a blocking call and it does what it says - waits till motion on all motors complete." ] }, { "cell_type": "code", "execution_count": null, "id": "c9f23db3", "metadata": {}, "outputs": [], "source": [ "controller.wait_till_motion_complete()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "159b576a", "metadata": {}, "source": [ "### Motor submodules on the Controller" ] }, { "cell_type": "markdown", "id": "0d6a76b7", "metadata": {}, "source": [ "There are 3 motor submodules on the DMC4133 controller driver each corresponding to a motor attached to the controller. They are named `motor_a`, `motor_b` and `motor_c`. All the submodules are identical except for the motor axis they control. Can be accessed as follows:" ] }, { "cell_type": "code", "execution_count": null, "id": "25ff98bc", "metadata": {}, "outputs": [], "source": [ "A = controller.motor_a\n", "B = controller.motor_b\n", "C = controller.motor_c" ] }, { "cell_type": "markdown", "id": "73220e00", "metadata": {}, "source": [ "Let us take the example of motor A and see what all features are there on each submodule.\n", "\n", "While setting up the motion on the motor, `relative_position` parameter will help set the position to the motor where the motion should end as relative to the current position." ] }, { "cell_type": "code", "execution_count": null, "id": "87791714", "metadata": {}, "outputs": [], "source": [ "A.relative_position(2000) #values are in quadrature counts" ] }, { "cell_type": "markdown", "id": "170e1667", "metadata": {}, "source": [ "Set relative position can be found out by the following call." ] }, { "cell_type": "code", "execution_count": null, "id": "193f6d63", "metadata": {}, "outputs": [], "source": [ "A.relative_position()" ] }, { "cell_type": "markdown", "id": "37cc1bee", "metadata": {}, "source": [ "Motor's motion `speed`, `acceleration` and `deceleration` can be set with parameters as follows." ] }, { "cell_type": "code", "execution_count": null, "id": "af767913", "metadata": {}, "outputs": [], "source": [ "A.speed(2000) #value in quadrature counts per sec and should be a multiple of 2\n", "A.acceleration(2048) #value in quadrature counts per sec sq. and should be a multiple of 1024\n", "A.deceleration(2048) # same as acceleration" ] }, { "cell_type": "markdown", "id": "a4aff838", "metadata": {}, "source": [ "Now, the motion parameters are set. Before commanding the motor to move we need to switch on them using the following method call." ] }, { "cell_type": "code", "execution_count": null, "id": "2cbd4f14", "metadata": {}, "outputs": [], "source": [ "A.servo_here()" ] }, { "cell_type": "markdown", "id": "3dd240d1", "metadata": {}, "source": [ "After this motor can be commanded to move with.." ] }, { "cell_type": "code", "execution_count": null, "id": "827c2360", "metadata": {}, "outputs": [], "source": [ "A.begin()" ] }, { "cell_type": "markdown", "id": "d3565601", "metadata": {}, "source": [ "We can check if a motor is in motion with following command." ] }, { "cell_type": "code", "execution_count": null, "id": "22e63cd1", "metadata": {}, "outputs": [], "source": [ "A.is_in_motion() # returns 1 if in motion otherwise 0" ] }, { "cell_type": "markdown", "id": "c6575e74", "metadata": {}, "source": [ "A blocking wait command can be given which waits till the motor's motion completes." ] }, { "cell_type": "code", "execution_count": null, "id": "9952e65b", "metadata": {}, "outputs": [], "source": [ "A.wait_till_motor_motion_complete()" ] }, { "cell_type": "markdown", "id": "313f88b1", "metadata": {}, "source": [ "To switch off the motor, call.." ] }, { "cell_type": "code", "execution_count": null, "id": "0cab19a1", "metadata": {}, "outputs": [], "source": [ "A.off()" ] }, { "cell_type": "markdown", "id": "5fddc10d", "metadata": {}, "source": [ "In order to find out if the motor is in on or off state, we can use following method." ] }, { "cell_type": "code", "execution_count": null, "id": "c4cb8bfc", "metadata": {}, "outputs": [], "source": [ "A.on_off_status()" ] }, { "cell_type": "markdown", "id": "a30ec29a", "metadata": {}, "source": [ "For each motor, reverse and forward software limits can be set with the help of `reverse_sw_limit` and `forward_sw_limit` parameters respectively. The values should be given in quadrature counts. The motor will not move beyond these set limits." ] }, { "cell_type": "markdown", "id": "7daf7148", "metadata": {}, "source": [ "Each motor can be set to turn off when an error occurs with the following parameter." ] }, { "cell_type": "code", "execution_count": null, "id": "a920a347", "metadata": {}, "outputs": [], "source": [ "A.off_when_error_occurs(\"disable\") # possible arguments are: \"disable\",\n", " # \"enable for position, amp error or abort\"\n", " # \"enable for hw limit switch\"\n", " # \"enable for all\"" ] }, { "cell_type": "markdown", "id": "a9a88d1e", "metadata": {}, "source": [ "Error magnitude can be checked on each motor with following method." ] }, { "cell_type": "code", "execution_count": null, "id": "5e61c7ea", "metadata": {}, "outputs": [], "source": [ "A.error_magnitude()" ] }, { "cell_type": "markdown", "id": "64293957", "metadata": {}, "source": [ "## Vector mode submodules on the Controller" ] }, { "cell_type": "markdown", "id": "3a269c36", "metadata": {}, "source": [ "On DMC4133 Controller there are three vector mode submodules present for planer movement in `AB`, `BC` and `AC` planes. They can be accessed as follows." ] }, { "cell_type": "code", "execution_count": null, "id": "0aa4157b", "metadata": {}, "outputs": [], "source": [ "AB = controller.plane_ab\n", "BC = controller.plane_bc\n", "AC = controller.plane_ac" ] }, { "cell_type": "markdown", "id": "d844d0de", "metadata": {}, "source": [ "Let us consider `BC` plane to see what all features are available on the driver. Before we setup the motion, that plane needs to be activated with following method call." ] }, { "cell_type": "code", "execution_count": null, "id": "897d6123", "metadata": {}, "outputs": [], "source": [ "BC.activate()" ] }, { "cell_type": "markdown", "id": "07823e65", "metadata": {}, "source": [ "There are two possible coordinate systems `S` and `T` which can be set for the movement." ] }, { "cell_type": "code", "execution_count": null, "id": "a688f4fe", "metadata": {}, "outputs": [], "source": [ "BC.coordinate_system('S')" ] }, { "cell_type": "markdown", "id": "adaf8f21", "metadata": {}, "source": [ "Now, let us set up the motion on this plane." ] }, { "cell_type": "code", "execution_count": null, "id": "7c56426c", "metadata": {}, "outputs": [], "source": [ "# all units are in quadrature counts\n", "\n", "BC.vector_position(2000,3000) # first coordinate corresponds to B axis and second to C axis in this case\n", "BC.vector_speed(2000)\n", "BC.vector_acceleration(2048)\n", "BC.vector_deceleration(2048)\n", "BC.vector_seq_end() # this call is necessary to exit the vector mode gracefully" ] }, { "cell_type": "markdown", "id": "3c0fea3b", "metadata": {}, "source": [ "Since the motion is setup, we can instruct the motors to move with following call." ] }, { "cell_type": "code", "execution_count": null, "id": "db47148f", "metadata": {}, "outputs": [], "source": [ "BC.begin_seq()" ] }, { "cell_type": "markdown", "id": "cf051549", "metadata": {}, "source": [ "To clear the sequence of commands from a given coordinate system, use:" ] }, { "cell_type": "code", "execution_count": null, "id": "62a034ed", "metadata": {}, "outputs": [], "source": [ "BC.clear_sequence('S')" ] }, { "cell_type": "markdown", "id": "5701e119", "metadata": {}, "source": [ "Our tour of the `DMC4133Controller` class ends here. Lets move on to `Arm` Class. But before moving forward, keep in mind the following assumptions:\n", "\n", " 1. Needle arm head is assumed to be rectangular in shape with one or more rows and each row with one or more needles.\n", " 2. Chip to be probed is assumed to be rectangular as well with number of rows in it to be multiple of number of rows in the needle head and number of pads in each row to be a multiple of number of needles in each row of the needle head. " ] }, { "attachments": {}, "cell_type": "markdown", "id": "82e35cc3", "metadata": {}, "source": [ "## Arm class" ] }, { "cell_type": "code", "execution_count": 3, "id": "89610c52", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:18:57.715235Z", "start_time": "2021-09-06T12:18:57.705231Z" } }, "outputs": [], "source": [ "arm = GalilDMC4133Arm(controller)" ] }, { "cell_type": "markdown", "id": "9c968a25", "metadata": {}, "source": [ "Now that we have imported an initialized the controller and the arm. We need to calibrate the arm." ] }, { "attachments": {}, "cell_type": "markdown", "id": "78869855", "metadata": {}, "source": [ "## Calibration" ] }, { "cell_type": "markdown", "id": "8d66d4b5", "metadata": {}, "source": [ "Check the state of the motors." ] }, { "cell_type": "code", "execution_count": 62, "id": "2442e7a1", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:46:08.312660Z", "start_time": "2021-09-06T12:46:08.282613Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'A': -90000, 'B': -10000, 'C': -16000}\n", "on\n", "on\n", "on\n" ] } ], "source": [ "print(controller.absolute_position())\n", "print(controller.motor_a.on_off_status())\n", "print(controller.motor_b.on_off_status())\n", "print(controller.motor_c.on_off_status())" ] }, { "cell_type": "code", "execution_count": 5, "id": "c5aade6d", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:19:49.659764Z", "start_time": "2021-09-06T12:19:49.649603Z" } }, "outputs": [], "source": [ "controller.motors_off()" ] }, { "cell_type": "code", "execution_count": 6, "id": "e7e2eb5b", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:19:53.183446Z", "start_time": "2021-09-06T12:19:53.163445Z" } }, "outputs": [], "source": [ "arm.set_arm_kinematics() # sets default values of arm speed to be 100 micro meters per second,\n", " # acceleration and deceleration to be 2048 micro meters per second square" ] }, { "cell_type": "markdown", "id": "d73781eb", "metadata": {}, "source": [ "Manually move the motors and take the needle head to the position where you want to set the origin. This will be the left bottom corner of the chip and run following commands." ] }, { "cell_type": "code", "execution_count": 7, "id": "1c0ac718", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:20:57.094423Z", "start_time": "2021-09-06T12:20:57.084417Z" } }, "outputs": [], "source": [ "controller.define_position_as_origin()" ] }, { "cell_type": "code", "execution_count": 54, "id": "a6389d1d", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:43:16.312056Z", "start_time": "2021-09-06T12:43:16.302053Z" } }, "outputs": [], "source": [ "arm.set_left_bottom_position()" ] }, { "cell_type": "markdown", "id": "9c1b17cc", "metadata": {}, "source": [ "From now on all the motor movements will be controlled by the driver commands.\n", "\n", "Next step is to set reverse limits for for all three motors. First take the motor C to the extreme reverse position which you want to set as the reverse limit with following command." ] }, { "cell_type": "code", "execution_count": 29, "id": "886c7029", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:31:01.319932Z", "start_time": "2021-09-06T12:30:51.255819Z" } }, "outputs": [], "source": [ "arm.move_motor_c_by(distance=-1000) # distance is in micro meters" ] }, { "cell_type": "markdown", "id": "a3f35ea7", "metadata": {}, "source": [ "Now that you are at the position which you want to set as the reverse limit for motor C, run the following command." ] }, { "cell_type": "code", "execution_count": 30, "id": "c56dd712", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:31:05.893806Z", "start_time": "2021-09-06T12:31:05.883801Z" } }, "outputs": [], "source": [ "arm.set_motor_c_reverse_limit()" ] }, { "cell_type": "markdown", "id": "ac25c254", "metadata": {}, "source": [ "For setting forward limit following command can be run." ] }, { "cell_type": "code", "execution_count": null, "id": "44bfdda4", "metadata": {}, "outputs": [], "source": [ "arm.set_motor_c_forward_limit()" ] }, { "cell_type": "markdown", "id": "3473375b", "metadata": {}, "source": [ "Repeat the same process for motor A but you need to set both forward and reverse limits at the desired locations." ] }, { "cell_type": "code", "execution_count": 24, "id": "54f93a57", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:28:57.360487Z", "start_time": "2021-09-06T12:28:55.303515Z" } }, "outputs": [], "source": [ "arm.move_motor_a_by(distance=-200)" ] }, { "cell_type": "code", "execution_count": null, "id": "3b9b08cd", "metadata": {}, "outputs": [], "source": [ "arm.set_motor_a_forward_limit()" ] }, { "cell_type": "code", "execution_count": 21, "id": "798ee956", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:28:12.429647Z", "start_time": "2021-09-06T12:28:12.419639Z" } }, "outputs": [], "source": [ "arm.set_motor_a_reverse_limit()" ] }, { "cell_type": "markdown", "id": "a25eef97", "metadata": {}, "source": [ "Now, for motor B. Again both forward and reverse limits need to be set at desired locations." ] }, { "cell_type": "code", "execution_count": 14, "id": "86fbbec1", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:24:50.575746Z", "start_time": "2021-09-06T12:24:30.503480Z" } }, "outputs": [], "source": [ "arm.move_motor_b_by(distance=-2000)" ] }, { "cell_type": "code", "execution_count": null, "id": "27db8105", "metadata": {}, "outputs": [], "source": [ "arm.set_motor_b_forward_limit()" ] }, { "cell_type": "code", "execution_count": 15, "id": "e13848dc", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:25:02.851306Z", "start_time": "2021-09-06T12:25:02.831299Z" } }, "outputs": [], "source": [ "arm.set_motor_b_reverse_limit()" ] }, { "cell_type": "markdown", "id": "e49b0def", "metadata": {}, "source": [ "You have set the reverse limits for all three motors. Next we will define the chip plane. We have already set the chip left bottom corner as the origin of the system. Now, we will set the left top corner first and then right top corner.\n", "\n", "Move individual motors with following commands." ] }, { "cell_type": "code", "execution_count": 58, "id": "c2af8774", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:45:02.639168Z", "start_time": "2021-09-06T12:44:52.575219Z" } }, "outputs": [], "source": [ "arm.move_motor_a_by(distance=-1000)" ] }, { "cell_type": "code", "execution_count": 59, "id": "af0ea403", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:45:43.336056Z", "start_time": "2021-09-06T12:45:13.265417Z" } }, "outputs": [], "source": [ "arm.move_motor_b_by(distance=3000)" ] }, { "cell_type": "code", "execution_count": 57, "id": "2033c1e8", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:44:43.247656Z", "start_time": "2021-09-06T12:44:40.184859Z" } }, "outputs": [], "source": [ "arm.move_motor_c_by(distance=-300)" ] }, { "cell_type": "markdown", "id": "6baaa29f", "metadata": {}, "source": [ "When you are satisfied that the motor is at the left top position of the chip. Run the following command." ] }, { "cell_type": "code", "execution_count": 46, "id": "0f0cca74", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:39:51.713616Z", "start_time": "2021-09-06T12:39:51.684018Z" } }, "outputs": [], "source": [ "arm.set_left_top_position()" ] }, { "cell_type": "markdown", "id": "88a2048f", "metadata": {}, "source": [ "Again, move invidual motors with the above mentioned commands and when you are satisfied that the arm needle is at the right top position of the chip, run the following command." ] }, { "cell_type": "code", "execution_count": 60, "id": "2ca55edc", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:45:51.672131Z", "start_time": "2021-09-06T12:45:51.632112Z" } }, "outputs": [], "source": [ "arm.set_right_top_position()" ] }, { "cell_type": "markdown", "id": "9b83588d", "metadata": {}, "source": [ "You have not set the boundaries for the motion of the motor. Though the calibration process is not complete yet. You need to set the chip details." ] }, { "attachments": {}, "cell_type": "markdown", "id": "76673e3f", "metadata": {}, "source": [ "### Set chip details" ] }, { "cell_type": "code", "execution_count": 61, "id": "829a050e", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:45:58.432335Z", "start_time": "2021-09-06T12:45:58.421312Z" } }, "outputs": [], "source": [ "arm.rows = 2\n", "arm.pads = 3\n", "arm.inter_row_distance = arm.norm_b # since there are only 2 rows\n", "arm.inter_pad_distance = arm.norm_c / 2 # since there are 3 pads per row" ] }, { "cell_type": "markdown", "id": "1a243bfc", "metadata": {}, "source": [ "Calibration is complete! Remember you are at the right top position of the chip crrently. Move the needle head to left bottom position and before that set pick up diatance." ] }, { "cell_type": "code", "execution_count": 64, "id": "7201c10e", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:49:10.646760Z", "start_time": "2021-09-06T12:49:10.626699Z" } }, "outputs": [], "source": [ "arm.set_pick_up_distance()" ] }, { "cell_type": "code", "execution_count": 65, "id": "e6d63e89", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:51:02.321140Z", "start_time": "2021-09-06T12:49:13.579828Z" } }, "outputs": [], "source": [ "arm.move_towards_left_bottom_position()" ] }, { "attachments": {}, "cell_type": "markdown", "id": "960dfa4c", "metadata": {}, "source": [ "## Integration with measurement process" ] }, { "cell_type": "code", "execution_count": 66, "id": "f1deca5b", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:51:13.586274Z", "start_time": "2021-09-06T12:51:13.556223Z" } }, "outputs": [], "source": [ "import qcodes as qc\n", "from qcodes.dataset import (\n", " Measurement,\n", " initialise_or_create_database_at,\n", " load_or_create_experiment,\n", ")\n", "from qcodes.instrument_drivers.mock_instruments import (\n", " DummyInstrument,\n", " DummyInstrumentWithMeasurement,\n", ")" ] }, { "cell_type": "code", "execution_count": 67, "id": "6e5067c3", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:51:14.367095Z", "start_time": "2021-09-06T12:51:14.347044Z" } }, "outputs": [], "source": [ "station = qc.Station()" ] }, { "cell_type": "code", "execution_count": 68, "id": "bcc6a594", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:51:15.107487Z", "start_time": "2021-09-06T12:51:15.087907Z" } }, "outputs": [], "source": [ "# A dummy instrument dac with two parameters ch1 and ch2\n", "dac = DummyInstrument('dac', gates=['ch1', 'ch2'])\n", "\n", "# A dummy instrument that generates some real looking output depending\n", "# on the values set on the setter_instr, in this case the dac\n", "dmm = DummyInstrumentWithMeasurement('dmm', setter_instr=dac)" ] }, { "cell_type": "code", "execution_count": 69, "id": "50a716fc", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:51:15.928741Z", "start_time": "2021-09-06T12:51:15.858640Z" } }, "outputs": [ { "data": { "text/plain": [ "'dmm'" ] }, "execution_count": 69, "metadata": {}, "output_type": "execute_result" } ], "source": [ "station.add_component(dac)\n", "station.add_component(dmm)" ] }, { "cell_type": "code", "execution_count": 70, "id": "c5922311", "metadata": { "ExecuteTime": { "end_time": "2021-09-06T12:51:17.370483Z", "start_time": "2021-09-06T12:51:17.310414Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Upgrading database; v8 -> v9: 100%|██████████████████████████████████████████████████████████████| 1/1 [00:00